﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing;
using Raider.WinForms.Actions.Linkage;
using Raider.WinForms.Actions.Utils;
using System.Threading;
using System.Runtime.Remoting.Contexts;

namespace Raider.WinForms.Actions
{
    /// <summary>
    /// Maintains a list of actions that can be used by components and controls, such as menu items and buttons.
    /// </summary>
    [Designer(Constants.ActionManagerDesigner)]
    [ProvideProperty("Action", typeof(IComponent))]
    [Description("Maintains a list of actions that can be used by components and controls, such as menu items and buttons.")]
    [DesignTimeVisible(true), ToolboxItem(true), ToolboxItemFilter("System.Windows.Forms")]
    [ToolboxBitmap(typeof(ActionManager), "ActionManager.bmp")]
    public class ActionManager : Component, ISupportInitialize, IExtenderProvider
    {
        private readonly ActionLinker actionLinker = new ActionLinker();
        private readonly ActionCollection actions = new ActionCollection();

        private bool enabled = true;
        private bool initialized;
        private bool suspended;
        private int lastUpdate;

        /// <summary>
        /// Initializes a new instance of the <see cref="ActionManager" /> class.
        /// </summary>
        public ActionManager()
        {
            MinimumUpdateInterval = 250;

            if (!DesignUtils.DesignMode)
            {
                Application.Idle += ApplicationIdle;
            }
        }

        /// <summary>
        /// Gets or sets the value defining a minimum interval between the automatic updates of the actions 
        /// when application is in idle state (in milliseconds).
        /// </summary>
        [DefaultValue(250)]
        [Category("Behavior"), Description("Defines a minimum interval between the updates.")]
        public int MinimumUpdateInterval
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the value indicating whether the action manager is enabled.
        /// </summary>
        /// <remarks>
        /// Disabled action manager does not update it's actions automatically, and does not route events
        /// from the client controls to actions.
        /// </remarks>
        [DefaultValue(true)]
        [Category("Behavior"), Description("Indicates whether the action manager is enabled.")]
        public bool Enabled
        {
            get { return enabled; }
            set 
            {
                enabled = value;
                actionLinker.Enabled = value;
            }
        }

        /// <summary>
        /// Gets the collection of actions maintained by the manager.
        /// </summary>
        /// <remarks>
        /// Returned collection contains a set of <b>unique</b> actions, and supports
        /// data binding.
        /// </remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public ICollection<IAction> Actions
        {
            get { return actions; }
        }

        /// <summary>
        /// Specifies whether this object can provide its extender properties to the
        /// specified object.
        /// </summary>
        public bool CanExtend(object extendee)
        {
            // TODO: Refactoring.
            return extendee is Control ||
                   extendee is ToolStripItem ||
                   extendee is MenuItem;
        }

        /// <summary>
        /// Links action with the specified component. If action is not maintained by the current 
        /// <see cref="ActionManager"/>, it will be added to <see cref="Actions"/> collection.
        /// </summary>
        /// <param name="client">Component to link the action with.</param>
        /// <param name="action">Action to link with the component (can be <c>null</c> to remove existing association).</param>
        public void SetAction(IComponent client, IAction action)
        {
            if (action == null)
            {
                actionLinker.UnLink(client);
            }
            else
            {
                actionLinker.Link(client, action);
                actions.Add(action);
            }
        }

        /// <summary>
        /// Gets the action linked with the specified component. Returns <c>null</c> if there is
        /// no such action.
        /// </summary>
        [DefaultValue(null)]
        [Category("Action"), Description("Defines the action associated with the component.")]
        public IAction GetAction(IComponent client)
        {
            return actionLinker[client];
        }

        /// <summary>
        /// Temporarily suspends automatic update of actions, maintained by the current manager.
        /// </summary>
        public void SuspendActionUpdates()
        {
            suspended = true;
        }

        /// <summary>
        /// Resumes automatic update of actions, maintained by the current manager.
        /// </summary>
        public void ResumeActionUpdates()
        {
            suspended = false;
        }

#if DEBUG
        /// <summary/>
        public void ShowTargetActionMap()
        {
            using (var form = new Form())
            {
                var list = new ListView
                    {
                        Dock = DockStyle.Fill,
                        GridLines = true,
                        View = View.Details,
                        FullRowSelect = true,
                        HeaderStyle = ColumnHeaderStyle.Nonclickable,
                    };

                list.Columns.AddRange(new[]
                    {
                        new ColumnHeader { Text = "Client", Width = 150 },
                        new ColumnHeader { Text = "Action", Width = -2 },
                    });

                foreach (var targetAndAction in actionLinker.targetActionMap)
                {
                    var item = list.Items.Add(targetAndAction.Key + 
                        " [" + targetAndAction.Key.GetType().Name + "]");

                    var action = targetAndAction.Value.ToString();
                    item.SubItems.Add(action);
                }

                form.Controls.Add(list);
                form.Size = new Size(400, 500);
                form.StartPosition = FormStartPosition.CenterScreen;
                form.ShowDialog();
            }
        }
#endif

        /// <summary>
        /// Disposes all actions from <see cref="Actions"/> collection.
        /// </summary>
        public void DisposeActions()
        {
            for (int i = actions.Count - 1; i >= 0; i--)
                actions[i].Dispose();

            actions.Clear();
        }

        #region ISupportInitialize Implementation

        void ISupportInitialize.BeginInit()
        {
            initialized = false;
            actionLinker.Enabled = false;
        }

        void ISupportInitialize.EndInit()
        {
            actionLinker.Enabled = enabled;
            initialized = true;
        } 

        #endregion

        private void ApplicationIdle(object sender, EventArgs e)
        {
            if (!initialized || !enabled || suspended) 
                return;

            // Do not update if form is minimized or not active.
            var activeForm = Form.ActiveForm;
            if (activeForm != null && activeForm.WindowState != FormWindowState.Minimized)
            {
                // Do not update too frequently.
                if (Environment.TickCount - lastUpdate > MinimumUpdateInterval)
                {
                    // When lastUpdate is -1, we should skip one cycle.
                    if (lastUpdate >= 0)
                    {
                        var focusedControl = ControlUtils.GetFocusedControl(activeForm);

                        // Use FOR loop because original collection may be changed 
                        // during the enumeration.
                        for (int i = 0; i < actions.Count && enabled && !suspended; i++)
                        {
                            if (actions[i].UpdateKind == ActionUpdateKind.Automatic)
                            {
                                actions[i].DoUpdate(focusedControl);
                            }
                        }
                    }

                    lastUpdate = Environment.TickCount;
                }
            }
            else
            {
                lastUpdate = -1;
            }
        }

        /// <summary/>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                Application.Idle -= ApplicationIdle;
                Actions.Clear();
            }

            base.Dispose(disposing);
        }
    }
}
